﻿namespace Pyramid
{
    using System;

    /// <summary>
    /// Represents a 9-trit balanced ternary integer.
    /// </summary>
    public partial struct Tryte
    {
        /// <summary>
        /// The maximum numeric value that a tryte can store.
        /// </summary>
        private const short MAX_VALUE = 9841; //(3 ^ 9 - 1) / 2

        /// <summary>
        /// The minimum numeric value that a tryte can store.
        /// </summary>
        private const short MIN_VALUE = -MAX_VALUE;

        /// <summary>
        /// Represents the largest possible value of a
        /// <see cref="Setun.Tryte"/>. This field is read-only.
        /// </summary>
        public static readonly Tryte MaxValue = new Tryte(MAX_VALUE);

        /// <summary>
        /// Represents the smallest possible value of a
        /// <see cref="Setun.Tryte"/>. This field is read-only.
        /// </summary>
        public static readonly Tryte MinValue = new Tryte(MIN_VALUE);

        /// <summary>
        /// The number of balanced ternary digits per tryte. This field is constant.
        /// </summary>
        public const int TritsPerTryte = 9;

        private readonly short value;

        private Tryte(short value)
        {
            Util.AssertRange(value, MIN_VALUE, MAX_VALUE);

            this.value = value;
        }

        public static bool TryParse(string s, out Tryte result)
        {
            s = s.ToUpperInvariant();

            // TODO This currently throws exceptions on Ternary or Nonary format
            // errors; being a TryXXX method, it should return false instead.

            if (s.StartsWith(Util.TERNARY_FORMAT_PREFIX))
            {
                return TryParseTernary(s, out result);
            }
            else if (s.StartsWith(Util.NONARY_FORMAT_PREFIX))
            {
                return TryParseNonary(s, out result);
            }
            else
            {
                return TryParseShort(s, out result);
            }
        }

        private static bool TryParseShort(string s, out Tryte result)
        {
            short parsedValue;

            if (short.TryParse(s, out parsedValue) && parsedValue <= MAX_VALUE && parsedValue >= MIN_VALUE)
            {
                result = new Tryte(parsedValue);
                return true;
            }
            else
            {
                result = 0;
                return false;
            }
        }

        private static bool TryParseTernary(string s, out Tryte result)
        {
            long parsedValue = Util.ParseTernaryString(s);

            if (parsedValue <= MAX_VALUE && parsedValue >= MIN_VALUE)
            {
                result = new Tryte((short)parsedValue);
                return true;
            }
            else
            {
                result = 0;
                return false;
            }
        }

        private static bool TryParseNonary(string s, out Tryte result)
        {
            long parsedValue = Util.ParseNonaryString(s);

            if (parsedValue <= MAX_VALUE && parsedValue >= MIN_VALUE)
            {
                result = new Tryte((short)parsedValue);
                return true;
            }
            else
            {
                result = 0;
                return false;
            }
        }

        public static Tryte Parse(string s)
        {
            Tryte result;

            if (TryParse(s, out result))
            {
                return result;
            }
            else
            {
                throw new FormatException("Input string was not in a correct format.");
            }
        }

        #region Implicit/Explicit Operators

        [CLSCompliant(false)]
        public static implicit operator Tryte(sbyte value)
        {
            return new Tryte(value);
        }

        [CLSCompliant(false)]
        public static explicit operator sbyte(Tryte value)
        {
            return (sbyte)value.value;
        }

        public static explicit operator Tryte(short value)
        {
            return new Tryte((short)Util.Trim(value, MAX_VALUE, MIN_VALUE));
        }

        public static implicit operator short(Tryte value)
        {
            return value.value;
        }

        public static explicit operator Tryte(int value)
        {
            return new Tryte((short)Util.Trim(value, MAX_VALUE, MIN_VALUE));
        }

        public static implicit operator int(Tryte value)
        {
            return value.value;
        }

        public static explicit operator Tryte(long value)
        {
            return new Tryte((short)Util.Trim(value, MAX_VALUE, MIN_VALUE));
        }

        public static implicit operator long(Tryte value)
        {
            return value.value;
        }

        #endregion

        public override string ToString()
        {
            return value.ToString();
        }

        public static Tryte And(Tryte left, Tryte right)
        {
            return ApplyBitwiseOperator(left, right, Trivalent.And);
        }

        public static Tryte Mask(Tryte left, Tryte right)
        {
            return ApplyBitwiseOperator(left, right, Trivalent.Mask);
        }

        private static Tryte ApplyBitwiseOperator(Tryte left, Tryte right, Func<Trivalent, Trivalent, Trivalent> bitwiseOperator)
        {
            var leftTrits = left.ToTritArray();
            var rightTrits = right.ToTritArray();

            Tryte result = 0;

            for (int tritIndex = 0; tritIndex < TritsPerTryte; tritIndex++)
            {
                result += (Tryte)((short)bitwiseOperator(leftTrits[tritIndex], rightTrits[tritIndex]) * Math.Pow(3, tritIndex));
            }

            return result;
        }
    }

    public partial struct Tryte
        : ITernary
    {
        public int TritSize
        {
            get
            {
                return TritsPerTryte;
            }
        }

        public Trivalent[] ToTritArray()
        {
            return Util.ToTritArray(this.value, TritsPerTryte);
        }

        public Tryte ToTryte()
        {
            return this;
        }

        public Int27 ToInt27()
        {
            return (Int27)this.value;
        }
    }

    public partial struct Tryte
        : IComparable
    {
        #region IComparable Members

        public int CompareTo(object obj)
        {
            return Util.CompareTo(this, obj);
        }

        #endregion
    }

    public partial struct Tryte
        : IComparable<Tryte>
    {
        #region IComparable<Tryte> Members

        public int CompareTo(Tryte other)
        {
            return this.value.CompareTo(other.value);
        }

        #endregion

        public static explicit operator Tryte(Int27 value)
        {
            return new Tryte((short)Util.Trim(value, MAX_VALUE, MIN_VALUE));
        }

        public static implicit operator Int27(Tryte value)
        {
            return value.value;
        }
    }
}
